using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using T.FAS.Runtime.Base.Entity;
using T.FAS.Runtime.Metadata.Entity.DataModel;
using T.FAS.Runtime.Metadata.SPI;
using T.FAS.Runtime.Metadata.Client;
using AppContext = T.ServiceComponent.Session.Service.AppContext;
using T.FAS.Runtime.Expression.Service;

namespace T.FAS.Runtime.StateMachine.Engine
{
   public partial class StateMachine
    {
        private IMetaDataClientService metaDataClientService = MetaDataClientService.GetInstance();
        /// <summary>
        /// 调用来源，如果为AF则跳过权限校验
        /// </summary>
        private const string InvokeSource = "Source";

        /// <summary>
        /// 是否可以触发事件
        /// </summary>
        /// <param name="metadata">状态机元数据</param>
        /// <param name="eventKey">事件标识</param>
        /// <param name="record">CDO记录信息</param>
        /// <param name="state">当前TRecord状态字段</param>
        /// <param name="timing">状态机调用时机</param>
        /// <param name="extParams">权限校验参数</param>
        /// <returns></returns>
        public static bool CanFire(StateMachineMetadata metadata, 
                                    string eventKey, 
                                    TRecord record, 
                                    out object state, 
                                    ActionExecuteTiming timing = ActionExecuteTiming.BeforeSave,
                                    Dictionary<string, object> extParams = null)
        {
            state = null;
            ValidateData(metadata);
            if (string.IsNullOrEmpty(eventKey) || record == null)
                throw new FasException(SMErrorCode.Error, string.Format(CommonStringClass.StateMachine_CanFire,metadata?.Code));
            if (metadata.Events.Where(e => e.Key == eventKey).Count() == 0) //EventKey不在当前状态机中
                throw new FasException(SMErrorCode.Error, string.Format(CommonStringClass.StateMachine_CanFire1,metadata?.Code,eventKey));
            //处理状态机权限
            var isCheckPermission = IsNeedCheckPermission(extParams);
            if (isCheckPermission && !ValidAuthPermission(metadata, eventKey))
            {
                var msg = string.Format(CommonStringClass.StateMachine_CanFire2, metadata?.Code, UserUtil.GetCurrentUserID(), UserUtil.GetCurrentUserName(), eventKey);
                LocalLogService.WriteError(new { Msg = msg, Param = extParams });
                throw new FasException(SMErrorCode.Error, msg);
            }

            var recordState = GetRecordState(metadata, record);
            state = recordState;

            var transition = GetTransition(metadata, eventKey);
            if (timing == ActionExecuteTiming.StateChanging)
            {
                if (transition.From == null)
                {  //初始事件
                    if (recordState == null || string.IsNullOrEmpty(recordState.ToString()))
                        return true;
                    else
                        return false;
                }
                else
                    return CompareState(transition.From?.Key, recordState);    //强制类型转换比较
            }
            else if (timing == ActionExecuteTiming.BeforeSave || timing == ActionExecuteTiming.AfterSave)
            {
                return CompareState(transition.To?.Key, recordState);    //强制类型转换比较
            }
            else
                return false;
        }

        /// <summary>
        /// 执行状态切换时动作
        /// </summary>
        /// <param name="eventKey">事件标识</param>
        /// <param name="record">CDO结果对象</param>
        /// <param name="args">参数</param>
        /// <returns></returns>
        public static TRecord FireStateChanging(StateMachineMetadata metadata, string eventKey, TRecord record, Dictionary<string, object> extParams = null)
        {
            ActionExecuteTiming executeTiming = ActionExecuteTiming.StateChanging;
            if (CanFire(metadata, eventKey, record, out object state, executeTiming, extParams) == false)
                throw new FasException(SMErrorCode.StateNotMatchError, string.Format(CommonStringClass.StateMachine_FireStateChanging1,(state ?? CommonStringClass.StateMachine_FireStateChanging),eventKey,TRecord.ToJson(record)));
            var transition = GetTransition(metadata, eventKey);
            try
            {
                List<TAction> NeedExecuteActions = transition.Actions.Where(action => action.ActionExecuteTiming == executeTiming).ToList();
                var result = ActionController.Current.Do(record, NeedExecuteActions, metadata);
                //Action执行成功后，变更record状态
                var nextStateKey = metadata.States.FirstOrDefault(s => s.ID == transition.ToStateID).Key;
                record.SetValue(metadata.CDOColKey, nextStateKey);
                return result;
            }
            catch (FasException e)
            {

                throw new FasException(SMErrorCode.Error, string.Format(CommonStringClass.StateMachine_FireStateChanging2,e.Message), e);
            }
        }

        /// <summary>
        /// 执行状态保存前动作
        /// </summary>
        /// <param name="eventKey">事件标识</param>
        /// <param name="record">CDO结果对象</param>
        /// <param name="args">参数</param>
        /// <returns></returns>
        public static TRecord FireBeforeSave(StateMachineMetadata metadata, string eventKey, TRecord record, Dictionary<string, object> extParams = null)
        {
            ActionExecuteTiming executeTiming = ActionExecuteTiming.BeforeSave;
            if (CanFire(metadata, eventKey, record, out object state, executeTiming, extParams) == false)
                throw new FasException(SMErrorCode.Error, string.Format(CommonStringClass.StateMachine_FireBeforeSave1,(state ?? CommonStringClass.StateMachine_FireBeforeSave),eventKey,TRecord.ToJson(record)));
            var transition = GetTransition(metadata, eventKey);
            try
            {
                List<TAction> NeedExecuteActions = transition.Actions.Where(action => action.ActionExecuteTiming == executeTiming).ToList();
                var result = ActionController.Current.Do(record, NeedExecuteActions, metadata);
                ////Action执行成功后，变更record状态
                //var nextStateKey = metadata.States.FirstOrDefault(s => s.ID == transition.ToStateID).Key;
                //record.SetValue(metadata.CDOColKey, nextStateKey);
                return result;
            }
            catch (FasException e)
            {

                throw new FasException(SMErrorCode.Error, string.Format(CommonStringClass.StateMachine_FireBeforeSave2,e.Message), e);
            }
        }

        /// <summary>
        /// 执行状态保存后动作
        /// </summary>
        /// <param name="eventKey">事件标识</param>
        /// <param name="record">CDO结果对象</param>
        /// <param name="args">参数</param>
        /// <returns></returns>
        public static TRecord FireAfterSave(StateMachineMetadata metadata, string eventKey, TRecord record, Dictionary<string, object> extParams = null)
        {
            ActionExecuteTiming executeTiming = ActionExecuteTiming.AfterSave;
            if (CanFire(metadata, eventKey, record, out object state, executeTiming, extParams) == false)
                throw new FasException(SMErrorCode.Error, string.Format(CommonStringClass.StateMachine_FireAfterSave1,(state ?? CommonStringClass.StateMachine_FireAfterSave),eventKey,TRecord.ToJson(record)));
            var transition = GetTransition(metadata, eventKey);
            try
            {
                List<TAction> NeedExecuteActions = transition.Actions.Where(action => action.ActionExecuteTiming == executeTiming).ToList();
                var result = ActionController.Current.Do(record, NeedExecuteActions, metadata);
                //状态变换完成后Action执行成功后，不需要变更record状态
                return result;
            }
            catch (FasException e)
            {

                throw new FasException(SMErrorCode.Error, string.Format(CommonStringClass.StateMachine_FireAfterSave2,e.Message), e);
            }
        }

        /// <summary>
        /// 校验数据在当前状态下的合法性
        /// </summary>
        /// <param name="record"></param>
        /// <returns></returns>
        public static ValidResult IsValid(StateMachineMetadata metadata,TRecord record)
        {
            ValidateData(metadata);

            var recordState = GetRecordState(metadata,record);
            if (recordState == null) //状态不存在
                throw new FasException(SMErrorCode.Error, CommonStringClass.StateMachine_IsValid+ recordState);

            var state = metadata.States.FirstOrDefault(x => CompareState(x.Key, recordState));
            if(state == null)
                throw new FasException(SMErrorCode.Error, CommonStringClass.StateMachine_IsValid1+ recordState);
            return RuleController.Current.Check(record, state, metadata);
        }

        /// <summary>
        /// 获取下一个可用状态状态信息（包括当前状态）
        /// </summary>
        /// <param name="metadata"></param>
        /// <returns></returns>
        public static List<TState> GetTransitionStates(StateMachineMetadata metadata, object stateKey, TRecord record = null)
        {
            ValidateData(metadata);
            List<TTransition> transitions;  //状态机内当前单据状态可以访问的事件集合
            List<TTransition> passAuthTransitions = new List<TTransition>();  //所有通过权限（如果启用）校验的事件集合
            List<TTransition> finAuthTransitions = new List<TTransition>();   //所有最终可用的事件集合
            #region 合法性校验
            if (stateKey != null)
            {
                if (metadata.States.FirstOrDefault(x => CompareState(x.Key, stateKey)) == null)
                    throw new FasException(SMErrorCode.Error, CommonStringClass.StateMachine_GetTransitionStates+ stateKey);
                transitions = metadata.Transitions.Where(x => CompareState(x.From?.Key, stateKey)).ToList();
            }
            else
                transitions = metadata.Transitions.Where(x => x.From == null).ToList();

            if (transitions == null)
                return null;
            #endregion
            try
            {
                /* Todo
                //处理状态机权限，过滤用户可以看到的可访问状态集合
                if (!string.IsNullOrEmpty(metadata.BPOID))
                {
                    var opList = GetCurrentUserOPList(metadata.BPOID).Select(op => op.ButtonId);    //获取OP集合
                    foreach (var tran in transitions)
                    {
                        if (!string.IsNullOrEmpty(tran.Event.OPID))
                        {
                            if (opList.Contains(tran.Event.OPID))
                            {
                                passAuthTransitions.Add(tran);
                            }
                        }
                        else
                        {
                            passAuthTransitions.Add(tran);
                        }
                    }
                }
                else
                {
                    passAuthTransitions = transitions;
                }
                */
            }
            catch (FasException e)
            {
                throw new FasException(SMErrorCode.Error, string.Format(CommonStringClass.StateMachine_GetTransitionStates1,metadata.ID,metadata.Code,stateKey), e);
            }
            
            //执行事件前置条件校验，通过校验时才可选中该状态
            if (passAuthTransitions != null && passAuthTransitions.Count > 0)
            {
                var fasContext = new FASContext
                {
                    CurrentRecord = record,
                    PKColumnName = record?.PKColumnName,
                };
                Dictionary<string, object> expParam = new Dictionary<string, object>();
                foreach (var tran in passAuthTransitions)
                {
                    if (!string.IsNullOrEmpty(tran.Event.PreconditionExpression))
                    {
                        //如果前端获取状态机可访问的状态时，如果没有传入Record信息，则无法使用状态机前置规则校验。
                        if (record == null) continue;
                        var exprExecResult = ExprService.GetInstance.Execute(tran.Event.PreconditionExpressionJson, fasContext, expParam);
                        if (exprExecResult.State == Expression.Entity.ResultState.Fail)
                            throw new Exception(string.Format(CommonStringClass.StateMachine_GetTransitionStates2,tran.Event.Key,exprExecResult.ErrMsg));
                        else
                        {
                            if (bool.TryParse(exprExecResult.Result?.ToString(), out bool boolResult))
                            {
                                if (boolResult)
                                {
                                    finAuthTransitions.Add(tran);
                                }
                            }
                        }
                    }
                    else
                    {
                        finAuthTransitions.Add(tran);
                    }
                }
            }

            var toStates = finAuthTransitions.Select(x => x.To).OrderBy(x => x.Sort).ToList();
            var result = new List<TState>();
            result.AddRange(toStates);  //包含所有可到达的状态

            TState currentState = metadata.States.FirstOrDefault(x => CompareState(x.Key, stateKey));
            result.Add(currentState);   //包含当前状态，放到最后
            return result;
        }

        /// <summary>
        /// 根据事件key获取Transition
        /// </summary>
        /// <param name="metadata"></param>
        /// <param name="eventKey"></param>
        /// <returns></returns>
        public static TTransition GetTransition(StateMachineMetadata metadata, string eventKey)
        {
            ValidateData(metadata);
            if (metadata.Events.FirstOrDefault(x => string.Equals(x.Key, eventKey, StringComparison.OrdinalIgnoreCase)) == null)
                throw new FasException(SMErrorCode.Error, CommonStringClass.StateMachine_GetTransition+ eventKey);

            return metadata.Transitions.FirstOrDefault(x => string.Equals(x.Event.Key, eventKey, StringComparison.OrdinalIgnoreCase));
        }

        /// <summary>
        /// 获取状态机初始状态
        /// </summary>
        /// <param name="metadata">状态机元数据</param>
        /// <returns></returns>
        public static TState GetEntryState(StateMachineMetadata metadata)
        {
            ValidateData(metadata);
            var firstTran = metadata.Transitions.FirstOrDefault(tran => string.IsNullOrEmpty(tran.FromStateID));
            if (firstTran == null)
                throw new FasException(SMErrorCode.Error, string.Format(CommonStringClass.StateMachine_GetEntryState,metadata.Code));
            var state = metadata.States.FirstOrDefault(s => s.ID == firstTran.ToStateID);
            if (state == null)
                throw new FasException(SMErrorCode.Error, string.Format(CommonStringClass.StateMachine_GetEntryState1,firstTran.ToStateID));
            return state;
        }

        #region Private Functions
        private static object GetRecordState(StateMachineMetadata metadata, TRecord record)
        {
            string sKey = metadata.CDOColKey;
            return record.GetValue(sKey);
        }

        private static void ValidateData(StateMachineMetadata metadata)
        {
            if (metadata == null)
                throw new FasException(SMErrorCode.Error, CommonStringClass.StateMachine_ValidateData);
            if (metadata.Transitions == null || metadata.Transitions.Count <= 0)
                throw new FasException(SMErrorCode.Error, CommonStringClass.StateMachine_ValidateData1+ metadata.Code);
            if (metadata.States == null || metadata.States.Count <= 0)
                throw new FasException(SMErrorCode.Error, CommonStringClass.StateMachine_ValidateData2+ metadata.Code);
            if (metadata.Events == null || metadata.Events.Count <= 0)
                throw new FasException(SMErrorCode.Error, CommonStringClass.StateMachine_ValidateData3+ metadata.Code);
        }

        /// <summary>
        /// 状态比较方法
        /// </summary>
        /// <param name="stateMachineState"></param>
        /// <param name="recordState"></param>
        /// <returns></returns>
        private static bool CompareState(object stateMachineState, object recordState)
        {
            if (stateMachineState == null || recordState == null)
                return false;
            if (long.TryParse(stateMachineState.ToString(), out long smState) && long.TryParse(recordState.ToString(), out long rState))
                return smState == rState;
            return false;
        }

        /// <summary>
        /// 是否需要校验权限
        /// </summary>
        /// <param name="extParams"></param>
        /// <returns>是否校验权限,true:是 false:否</returns>
        private static bool IsNeedCheckPermission(Dictionary<string, object> extParams)
        {
            bool isCheckPermission = true;
            if (extParams != null)
            {
                if (extParams.ContainsKey(InvokeSource))
                {
                    string source = extParams[InvokeSource]?.ToString();
                    if (!string.IsNullOrEmpty(source) && source.ToUpper() == "AF")
                    {
                        isCheckPermission = false;
                    }
                }
            }

            return isCheckPermission;
        }
        #endregion

        #region Authority Funcs
        /*
        /// <summary>
        /// SG请求获取权限对象
        /// </summary>
        /// <param name="BPOID"></param>
        /// <returns></returns>
        private static List<ButtonData> GetCurrentUserOPList(string BPOID)
        {
            try
            {
                string userId = UserUtil.GetCurrentUserID();
                var client = new SGHttpClient(true);
                Dictionary<string, string> parameter = new Dictionary<string, string>();
                parameter.Add("userId", userId);
                parameter.Add("menuId", BPOID);
                TResponse<IList<ButtonData>> result = client.PostCurrentIDC<IList<ButtonData>>("SYSSrv-IButtonService-GetButtonByUserID", parameter, 10);
                List<ButtonData> buttonList = result?.data?.ToList() ?? new List<ButtonData>(); //ButtonData.ButtonID = OPID
                return buttonList;
            }
            catch (Exception e)
            {
                throw new FasException(SMErrorCode.Error, string.Format(CommonStringClass.StateMachine_GetCurrentUserOPList,BPOID,e.Message), e);
            }
        }
        */
        /// <summary>
        /// 校验权限信息
        /// </summary>
        /// <param name="stateMachine"></param>
        /// <returns>true:通过权限校验，false:未通过权限校验</returns>
        private static bool ValidAuthPermission(StateMachineMetadata stateMachine, string eventKey)
        {
            try
            {
                if (string.IsNullOrEmpty(stateMachine.BPOID))
                    return true;
                /* Todo
                var OPList = GetCurrentUserOPList(stateMachine.BPOID).Select(op => op.ButtonId);
                TEvent @event = stateMachine.Events?.FirstOrDefault(e => e.Key == eventKey);
                if (!string.IsNullOrEmpty(@event.OPID)) //如果Event启用了OP权限
                {
                    return OPList.Contains(@event.OPID);    //如果OPList包含当前event.OPID，表示当前用户有权限执行此事件
                }
                */
                return true;
            }
            catch (FasException e)
            {
                throw new FasException(SMErrorCode.Error, string.Format(CommonStringClass.StateMachine_ValidAuthPermission,stateMachine.ID,stateMachine.Code,eventKey), e);
            }
        }
        #endregion
    }

    public class ValidResult
    {
        public bool IsSuccess { get; set; }

        public string FailReason { get; set; }
    }

    /// <summary>
    /// Session工具
    /// </summary>
    public class UserUtil
    {
        /// <summary>
        /// 获取当前登录的用户ID
        /// </summary>
        /// <returns></returns>
        public static string GetCurrentUserID()
        {
            string userID;
            try
            {
                if (AppContext.Current?.Session?["UserId"] == null)
                    return "T";
                else
                    userID = Convert.ToString(AppContext.Current.Session["UserId"]);
            }
            catch
            {
                userID = "T";
            }
            return userID;
        }
        /// <summary>
        /// 获取当前登录用户的名称
        /// </summary>
        /// <returns></returns>
        public static string GetCurrentUserName(string defaultUserName = "")
        {
            string userName;
            try
            {
                if (AppContext.Current?.Session?["UserName"] == null) return "T";
                userName = Convert.ToString(AppContext.Current.Session["UserName"]);
                userName = !String.IsNullOrEmpty(userName) ? userName : Convert.ToString(AppContext.Current.Session["Account"]);
                //如果用户名为空  且 默认用户名不为空 那么采用默认用户名
                if (string.IsNullOrEmpty(userName) == true)
                {
                    if (string.IsNullOrEmpty(defaultUserName) == false)
                    {
                        userName = defaultUserName;
                    }
                    else
                    {
                        userName = "T";
                    }
                }
            }
            catch (Exception ex)
            {
                if (string.IsNullOrEmpty(defaultUserName) == true)
                {
                    userName = "T";
                }
                else
                {
                    userName = defaultUserName;
                }
            }
            return userName;
        }
        /// <summary>
        /// 获取当前登录用户的租户ID
        /// </summary>
        /// <returns></returns>
        public static string GetCurrentUserTenantId()
        {
            string userID;
            try
            {
                if (AppContext.Current?.Session?["CompanyId"] == null) return "T";
                userID = Convert.ToString(AppContext.Current.Session["CompanyId"]);
            }
            catch
            {
                userID = "T";
            }
            return userID;
        }
    }
}
